/*
This is part of Screenbar, which is based in part on the Litestep
shell source code.

Copyright (C) 2008-2009 Jim Babcock
Copyright (C) 1997-2009 The LiteStep Development Team

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
*/
#include "stdafx.h"

typedef int (*ModuleInitExFunc)(HWND, HINSTANCE, LPCSTR);
typedef int (*ModuleQuitFunc)(HINSTANCE);

static void bangResizedEvent(HWND sender, LPCSTR bangCommandName, LPCSTR args);
map<string,ChildModuleElement*> eventModules;

ChildModuleElement::ChildModuleElement(string prefix)
	:RectLayoutElement(prefix)
{
	dllName = getConfigString("DLL", NULL, prefix.c_str());
	modulePrefix = getConfigString("Prefix", NULL, prefix.c_str());
	
	singleton = getConfigBool("Singleton", true, prefix.c_str());
	
	moveCommand   = getConfigLine("MoveCommand",   ("!"+modulePrefix+"Move").c_str(),   prefix.c_str());
	resizeCommand = getConfigLine("ResizeCommand", ("!"+modulePrefix+"Resize").c_str(), prefix.c_str());
	
	varX = getConfigLine("VarX", (modulePrefix+"X").c_str(), prefix.c_str());
	varY = getConfigLine("VarY", (modulePrefix+"Y").c_str(), prefix.c_str());
	
	string widthVars = getConfigLine("VarWidth",  (modulePrefix+"Width").c_str(),  prefix.c_str());
	string heightVars= getConfigLine("VarHeight", (modulePrefix+"Height").c_str(), prefix.c_str());
	
	tokenizeString(widthVars, varWidth, " ");
	tokenizeString(heightVars, varHeight, " ");
	
	resizedEventCommand = getConfigLine("ResizedEvent", "", prefix.c_str());
	if(resizedEventCommand != "") {
		AddBangCommandEx(resizedEventCommand.c_str(), bangResizedEvent);
		eventModules[resizedEventCommand] = this;
	}
}

ChildModuleElement::~ChildModuleElement()
{
	for(map<ScreenbarWindow*, LoadedModuleInstance*>::iterator ii = instances.begin(); ii != instances.end(); ii++)
	{
		LoadedModuleInstance *instance = ii->second;
		destroyInstance(instance);
	}
}

bool ChildModuleElement::changed(const LayoutCacheNode *node)
{
	LoadedModuleInstance *instance = getInstance(node);
	
	if(instance)
		return instance->varsChanged;
	
	return false;
}

void ChildModuleElement::arrangeChildren(LayoutCacheNode *container, vector<LayoutCacheNode*> &children)
{
	LoadedModuleInstance *instance = getInstance(container);
	if(instance) {
		updateVars(true, getRect(container), instance);
	} else {
		if(!singleton || !instances.size()) {
			createInstance(container);
		}
	}
}

ElementSize ChildModuleElement::getLength(const LayoutCacheNode *node)
{
	LoadedModuleInstance *instance = getInstance(node);
	if(!instance)
		return RectLayoutElement::getLength(node);
	
	int ret;
	if(useAspectRatio)
	{
		float ret = node->context.thickness;
		if(node->context.vertical)
			ret/=aspectRatio;
		else
			ret*=aspectRatio;
		
		return ElementSize(ret,ret);
	}
	else
	{
		if(node->context.vertical) {
			int contextHeight = node->boundingRect.height;
			ret = ParseCoordinate(instance->height.c_str(), contextHeight, contextHeight);
		} else {
			int contextWidth = node->boundingRect.width;
			ret = ParseCoordinate(instance->width.c_str(), contextWidth, contextWidth);
		}
		return ElementSize(ret, ret);
	}
}

Rect ChildModuleElement::getRect(const LayoutCacheNode *node)
{
	LoadedModuleInstance *instance = getInstance(node);
	if(!instance)
		return RectLayoutElement::getRect(node->boundingRect);
	
	Rect ret = node->boundingRect;
	
	if(useAspectRatio)
	{
		if(ret.width >= ret.height * aspectRatio)
			ret.width = ret.height * aspectRatio;
		else
			ret.height = ret.width / aspectRatio;
	}
	else
	{
		int contextWidth = ret.width;
		int contextHeight = ret.height;
		ret.left  += ParseCoordinate(x.c_str(), 0, contextWidth);
		ret.top   += ParseCoordinate(y.c_str(), 0, contextHeight);
		ret.width  = ParseCoordinate(instance->width.c_str(), contextWidth, contextWidth);
		ret.height = ParseCoordinate(instance->height.c_str(), contextHeight, contextHeight);
	}
	return ret;
}

void ChildModuleElement::updateVars(bool doBangs, Rect boundingRect, LoadedModuleInstance *instance)
{
	bool posChanged = false;
	bool sizeChanged = false;
	
	if(instance->lastX != boundingRect.left) {
		instance->lastX = boundingRect.left;
		posChanged = true;
	}
	if(instance->lastY != boundingRect.top) {
		instance->lastY = boundingRect.top;
		posChanged = true;
	}
	
	if(instance->lastWidth != boundingRect.width) {
		instance->lastWidth = boundingRect.width;
		sizeChanged = true;
	}
	if(instance->lastHeight != boundingRect.height) {
		instance->lastHeight = boundingRect.height;
		sizeChanged = true;
	}
	
	if(posChanged)
	{
		setConfigInt(varX.c_str(), boundingRect.left, "");
		setConfigInt(varY.c_str(), boundingRect.top, "");
		
		if(doBangs) {
			string command = retprintf("%s %i %i", moveCommand.c_str(), instance->lastX, instance->lastY);
			LSExecute(instance->containerWindow, command.c_str(), SW_SHOWNORMAL);
		}
	}
	if(sizeChanged)
	{
		for(unsigned ii=0; ii<varWidth.size(); ii++)
			setConfigInt(varWidth[ii].c_str(), boundingRect.width, "");
		for(unsigned ii=0; ii<varHeight.size(); ii++)
			setConfigInt(varHeight[ii].c_str(), boundingRect.height, "");
		
		if(doBangs) {
			if(resizeCommand != "") {
				string command = retprintf("%s %i %i", resizeCommand.c_str(), instance->lastWidth, instance->lastHeight);
				LSExecute(instance->containerWindow, command.c_str(), SW_SHOWNORMAL);
			}
		}
	}
	
	instance->varsChanged = false;
}

LoadedModuleInstance *ChildModuleElement::getInstance(const LayoutCacheNode *node)
{
	ScreenbarWindow *window = node->context.window;
	
	if(instances.find(window) != instances.end())
		return instances[window];
	else
		return NULL;
}

LoadedModuleInstance *ChildModuleElement::createInstance(LayoutCacheNode *node)
{
	LoadedModuleInstance *ret = new LoadedModuleInstance();
	
	ret->element = this;
	ret->containerWindow = node->context.window->getWindow();
	
	ret->width = width;
	ret->height = height;
	
	ret->lastX = ret->lastY = 0;
	ret->lastWidth = ret->lastHeight = 0;
	ret->varsChanged = true;
	
	ret->dllInstance = LoadLibraryA(dllName.c_str());
	if(!ret->dllInstance) {
		fatal(retprintf("Failed to load library: %s.", dllName.c_str()).c_str());
	}
	
	ret->initFunc = (ModuleInitExFunc)getModuleFunc(ret->dllInstance, "initModuleEx");
	ret->quitFunc = (ModuleQuitFunc)getModuleFunc(ret->dllInstance, "quitModule");
	
	updateVars(false, getRect(node), ret);
	
	ret->initFunc(ret->containerWindow, ret->dllInstance, modulePath.c_str());
	
	instances[node->context.window] = ret;
	return ret;
}

void ChildModuleElement::destroyInstance(LoadedModuleInstance *instance)
{
	instance->quitFunc(instance->dllInstance);
	delete instance;
}

void ChildModuleElement::resizedEvent(string newWidth, string newHeight)
{
	bool changed = false;
	
	for(map<ScreenbarWindow*, LoadedModuleInstance*>::iterator ii=instances.begin(); ii!=instances.end(); ii++)
	{
		LoadedModuleInstance *instance = ii->second;
		if(newWidth != instance->width || newHeight != instance->height)
		{
			instance->width = newWidth;
			instance->height = newHeight;
			instance->varsChanged = true;
			changed = true;
		}
	}
	
	if(changed)
		updateLayouts();
}

FARPROC ChildModuleElement::getModuleFunc(HINSTANCE dllInstance, const char *name)
{
	FARPROC ret = GetProcAddress(dllInstance, name);
	if(!ret) {
		string underscoredName = string("_")+name;
		ret = GetProcAddress(dllInstance, underscoredName.c_str());
	}
	return ret;
}

void bangResizedEvent(HWND sender, LPCSTR bangCommandName, LPCSTR args)
{
	if(eventModules.find(bangCommandName) == eventModules.end())
		return;

	vector<string> argv;

	if(args[0] == 0)
	{
		RECT r;
		GetWindowRect(sender, &r);
		argv = separateArgs(retprintf("%d %d", r.right-r.left, r.bottom-r.top).c_str());
	}
	else
	{
		argv = separateArgs(args);
	}

	if(argv.size() < 2)
		return;

	eventModules[bangCommandName]->resizedEvent(argv[0], argv[1]);
}
